﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Net;
using System.Text;

namespace Hourglass.Qrobot.Http
{
    public class HttpRequestHandler
    {
        private readonly IRetryManager _retryManager;

        public Uri Uri { get; set; }

        public string Host { get; set; }

        public HttpMethod Method { get; set; }

        public string Parameters { get; set; }

        public RequestAccept Accept { get; set; }

        public bool KeepAlive { get; set; }

        public string ContentType { get; set; }

        public string Referer { get; set; }

        public string UserAgent { get; set; }

        public IDictionary<string, string> Headers { get; set; }

        public ICollection<Cookie> Cookies { get; set; }

        public HttpRequestHandler(string url, IRetryManager retryManager = null)
        {
            this._retryManager = retryManager ?? new DefaultRetryManager();
            if (!url.StartsWith(QrobotConsts.HttpPrefix) && !url.StartsWith(QrobotConsts.HttpsPrefix))
            {
                url = QrobotConsts.HttpPrefix + url;
            }
            this.Uri = new Uri(url);
            DefaultInit();
        }

        public WebResponse DoRequest(CookieContainer cookieContainer = null)
        {
            this._retryManager.Reset();
            ProcessParameter();
    Retry:
            var request = MakeRequest(cookieContainer);
            WebResponse response;
            try
            {
                response = request.GetResponse();
            }
            catch (Exception e)
            {
                if (_retryManager.ExceptionRetry(this, request, e))
                {
                    goto Retry;
                }
                throw;
            }
            if (cookieContainer != null)
            {
                response.CookieMerge(cookieContainer);
            }
            return response;
        }

        public string RequestString(CookieContainer cookieContainer = null)
        {
            var response = DoRequest(cookieContainer);
            var content = response.GetStringContent(true);
            return content;
        }

        public T RequestJsonModel<T>(CookieContainer cookieContainer = null)
        {
            var response = DoRequest(cookieContainer);
            var model = response.GetJsonModel<T>(true);
            return model;
        }

        public Image RequestImage(CookieContainer cookieContainer = null)
        {
            var response = DoRequest(cookieContainer);
            var image = response.GetImage(true);
            return image;
        }

        public WebResponse DoGet(CookieContainer cookieContainer = null)
        {
            this.Method = HttpMethod.Get;
            return DoRequest(cookieContainer);
        }

        public WebResponse DoPost(CookieContainer cookieContainer = null)
        {
            this.Method = HttpMethod.Post;
            return DoRequest(cookieContainer);
        }

        public void AsyncRequest<T>(Action<T> callback, CookieContainer cookieContainer = null)
        {
            ProcessParameter();
            var request = MakeRequest(cookieContainer);
            request.BeginGetResponse(ar =>
            {
                var transRequest = (HttpWebRequest) ar.AsyncState;
                var response = transRequest.GetResponse();
                var result = response.GetJsonModel<T>();
                callback(result);
                transRequest.Abort();
            }, request);
        }

        #region Parameter Additional

        public HttpRequestHandler AddParameter(string key, string value)
        {
            if (string.IsNullOrEmpty(Parameters))
            {
                Parameters = $"{key}={value}";
            }
            else
            {
                Parameters += $"&{key}={value}";
            }
            return this;
        }

        public HttpRequestHandler AddHeader(string key, string value)
        {
            this.Headers.Add(key, value);
            return this;
        }

        public HttpRequestHandler AddCookie(string key, string value)
        {
            if (Uri == null) throw new TypeInitializationException(nameof(HttpRequestHandler), new NullReferenceException("Uri could not be null"));
            return AddCookie(key, value, "/", "." + Uri.Host);
        }

        public HttpRequestHandler AddCookie(string key, string value, string path, string domain)
        {
            var cookie = new Cookie(key, value, path, domain);
            return AddCookie(cookie);
        }

        public HttpRequestHandler AddCookie(Cookie cookie)
        {
            this.Cookies.Add(cookie);
            return this;
        }

        #endregion Parameter Additional

        #region Private Method

        private void ProcessParameter()
        {
            if (Method == HttpMethod.Get && !string.IsNullOrEmpty(Parameters))
            {
                var query = Uri.Query;
                if (string.IsNullOrEmpty(query) || query == "?")
                {
                    query = "?" + Parameters;
                }
                else
                {
                    query += "&" + Parameters;
                }
                var currentUrl = Uri.ToString();
                var url = currentUrl.Substring(0, currentUrl.Length - Uri.Query.Length);
                Uri = new Uri(url + query);
            }
        }

        private HttpWebRequest MakeRequest(CookieContainer cookieContainer)
        {
            var request = WebRequest.CreateHttp(Uri);
            request.Method = Method.ToCString();
            request.Host = Host;
            request.KeepAlive = KeepAlive;
            request.ContentType = ContentType;
            request.Referer = Referer;
            request.UserAgent = UserAgent;
            request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
            Accept.ApplyAccept(request);
            ApplyHeaders(request);
            ApplyCookies(request, cookieContainer);
            if (Method == HttpMethod.Post && !string.IsNullOrEmpty(Parameters))
            {
                var buffer = Encoding.Default.GetBytes(Parameters);
                request.ContentLength = buffer.Length;
                request.GetRequestStream().Write(buffer, 0, buffer.Length);
            }
            return request;
        }

        private HttpRequestHandler ApplyHeaders(HttpWebRequest request)
        {
            foreach (var header in Headers)
            {
                request.Headers.Add(header.Key, header.Value);
            }
            return this;
        }

        private HttpRequestHandler ApplyCookies(HttpWebRequest request, CookieContainer cookieContainer)
        {
            foreach (var cookie in Cookies)
            {
                cookieContainer.Add(cookie);
            }
            request.CookieContainer = cookieContainer;
            return this;
        }

        private void DefaultInit()
        {
            if (Uri == null) throw new TypeInitializationException(nameof(HttpRequestHandler), new NullReferenceException("Uri could not be null"));
            this.Host = this.Uri.Host;
            this.Method = HttpMethod.Get;
            this.Accept = new RequestAccept();
            this.KeepAlive = true;
            this.ContentType = "application/x-www-form-urlencoded";
            this.Referer = QrobotConsts.HttpPrefix + this.Host;
            this.UserAgent = QrobotConsts.DefaulUserAgent;
            this.Headers = new Dictionary<string, string>();
            this.Cookies = new List<Cookie>();
            if (this.Uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase))
            {
                this.Headers.Add("Origin", "http://" + Host);
            }
        }

        #endregion Private Method

        public enum HttpProtocol
        {
            Http,
            Https
        }

        public enum HttpMethod
        {
            Get,
            Post
        }

        public enum HttpConnection
        {
            KeepAlive,
            Close
        }
    }
}
